// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2013 - 2018 Intel Corporation. */

#ifdef CONFIG_DEBUG_FS

#include <linux/fs.h>
#include <linux/debugfs.h>

#include "i40e.h"
#include "i40e_cmd.h"

static char i40e_dbg_command_ext_buf[256] = "";

/**
 * i40e_dbg_command_read - read for command datum
 * @filp: the opened file
 * @buffer: where to write the data for the user to read
 * @count: the size of the user's buffer
 * @ppos: file position offset
 **/
static ssize_t i40e_dbg_command_ext_read(struct file *filp, char __user *buffer,
				     size_t count, loff_t *ppos)
{
	struct i40e_pf *pf = filp->private_data;
	int bytes_not_copied;
	int buf_size = 256;
	char *buf;
	int len;

	/* don't allow partial reads */
	if (*ppos != 0)
		return 0;
	if (count < buf_size)
		return -ENOSPC;

	buf = kzalloc(buf_size, GFP_KERNEL);
	if (!buf)
		return -ENOSPC;

	len = snprintf(buf, buf_size, "%s: %s\n",
		       pf->vsi[pf->lan_vsi]->netdev->name,
		       i40e_dbg_command_ext_buf);

	bytes_not_copied = copy_to_user(buffer, buf, len);
	kfree(buf);

	if (bytes_not_copied)
		return -EFAULT;

	*ppos = len;
	return len;
}

/**
 * i40e_dbg_command_ext_write - write into command datum
 * @filp: the opened file
 * @buffer: where to find the user's data
 * @count: the length of the user's data
 * @ppos: file position offset
 **/
static ssize_t i40e_dbg_command_ext_write(struct file *filp,
				      const char __user *buffer,
				      size_t count, loff_t *ppos)
{
	struct i40e_pf *pf = filp->private_data;
	char *cmd_buf, *cmd_buf_tmp;
	int bytes_not_copied;
	int cnt;
	u32 address;
	u64 value = 0;
	i40e_status stat;

	/* don't allow partial writes */
	if (*ppos != 0)
		return 0;
	if (count >= sizeof(i40e_dbg_command_ext_buf))
		return -ENOSPC;
	cmd_buf = kzalloc(count + 1, GFP_KERNEL);
	if (!cmd_buf)
		return -ENOSPC;
	bytes_not_copied = copy_from_user(cmd_buf, buffer, count);
	if (bytes_not_copied) {
		kfree(cmd_buf);
		return -EFAULT;
	}
	cmd_buf[count] = '\0';

	cmd_buf_tmp = strchr(cmd_buf, '\n');
	if (cmd_buf_tmp) {
		*cmd_buf_tmp = '\0';
		count = cmd_buf_tmp - cmd_buf + 1;
	}

	if (strncmp(cmd_buf, "force_down", strlen("force_down")) == 0) {
		dev_info(&pf->pdev->dev, "debugfs: forcing Down\n");
		stat = i40e_force_link_state(pf, false);
		if (stat == 0)
			dev_info(&pf->pdev->dev, "forcing Down successful.\n");
		else
			dev_info(&pf->pdev->dev, "forcing Down fail.\n");
	} else if (strncmp(cmd_buf, "force_up", strlen("force_up")) == 0) {
		dev_info(&pf->pdev->dev, "debugfs: forcing Up\n");
		stat = i40e_force_link_state(pf, true);
		if (stat == 0)
			dev_info(&pf->pdev->dev, "forcing Up successful.\n");
		else
			dev_info(&pf->pdev->dev, "forcing Up fail.\n");
	} else if (strncmp(cmd_buf, "fw_read", strlen("fw_read")) == 0) {
		cnt = kstrtouint(&cmd_buf[7], 0, &address);
		if (cnt != 1) {
			dev_info(&pf->pdev->dev, "fw_read <reg>\n");
			goto command_ext_write_done;
		}

		/* check the range on address */
		if (address > (pf->ioremap_len - sizeof(u32))) {
			dev_info(&pf->pdev->dev, "fw_read reg address 0x%08x too large, max=0x%08lx\n",
				address, (unsigned long)(pf->ioremap_len - sizeof(u32)));
			goto command_ext_write_done;
		}

		stat = i40e_aq_debug_read_register(&pf->hw, address, &value, NULL);
		if (stat == 0)
			dev_info(&pf->pdev->dev, "fw_read: 0x%08x = 0x%08llx\n", address, value);
		else
			dev_info(&pf->pdev->dev, "fw_read: 0x%08x fail.\n", address);
	} else if (strncmp(cmd_buf, "fw_write", strlen("fw_write")) == 0) {
		cnt = sscanf(&cmd_buf[8], "%i %lli", &address, &value);
		if (cnt != 2) {
			dev_info(&pf->pdev->dev, "fw_write <reg> <value>\n");
			goto command_ext_write_done;
		}

		/* check the range on address */
		if (address > (pf->ioremap_len - sizeof(u32))) {
			dev_info(&pf->pdev->dev, "fw_write reg address 0x%08x too large, max=0x%08lx\n",
				address, (unsigned long)(pf->ioremap_len - sizeof(u32)));
			goto command_ext_write_done;
		}
		stat = i40e_aq_debug_write_register(&pf->hw, address, value, NULL);
		if (stat == 0)
			dev_info(&pf->pdev->dev, "fw_write: 0x%08x = 0x%08llx\n", address, value);
		else
			dev_info(&pf->pdev->dev, "fw_write: 0x%08x fail.\n", address);
		stat = i40e_aq_debug_read_register(&pf->hw, address, &value, NULL);
		if (stat == 0)
			dev_info(&pf->pdev->dev, "fw_read: 0x%08x = 0x%08llx\n", address, value);
		else
			dev_info(&pf->pdev->dev, "fw_read: 0x%08x fail.\n", address);
		value = rd32(&pf->hw, address);
		dev_info(&pf->pdev->dev, "host read: 0x%08x = 0x%08llx\n", address, value);
	} else {
		dev_info(&pf->pdev->dev, "unknown command '%s'\n", cmd_buf);
		dev_info(&pf->pdev->dev, "available commands\n");
		dev_info(&pf->pdev->dev, "  force_down\n");
		dev_info(&pf->pdev->dev, "  force_up\n");
		dev_info(&pf->pdev->dev, "  fw_read <reg>\n");
		dev_info(&pf->pdev->dev, "  fw_write <reg> <value>\n");
	}

command_ext_write_done:
	kfree(cmd_buf);
	return count;
}

static const struct file_operations i40e_dbg_command_ext_fops = {
	.owner = THIS_MODULE,
	.open =  simple_open,
	.read =  i40e_dbg_command_ext_read,
	.write = i40e_dbg_command_ext_write,
};

void i40e_dbg_pf_init_extend(struct i40e_pf *pf)
{
	debugfs_create_file("command_extend", 0600, pf->i40e_dbg_pf, pf,
			    &i40e_dbg_command_ext_fops);
}

#endif /* CONFIG_DEBUG_FS */
